package com.myssm.myspringmvc;

import com.myssm.util.StringUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Jack Tang
 * @description
 * @create 2023-05-07 19:22
 */
@WebServlet("*.do") // 拦截所有以 .do 结尾的请求
public class DispatcherServlet extends ViewBaseServlet {

    private Map<String,Object> beanMap = new HashMap<>();
    public DispatcherServlet(){


    }

    public void init() throws ServletException {
        super.init();
        try {
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
            // 1.创建 DocumentBuilderFactory 对象
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            // 2.创建 DocumentBuilder 对象
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            // 3.创建 Document 对象
            Document document = documentBuilder.parse(inputStream);

            // 4.获取所有的bean节点
            NodeList beaNodeList = document.getElementsByTagName("bean");
            for (int i = 0; i < beaNodeList.getLength(); i++) {
                Node beaNode = beaNodeList.item(i);
                if (beaNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element beaElement = (Element) beaNode;
                    String beanId = beaElement.getAttribute("id");
                    String className = beaElement.getAttribute("class");
                    Class controllerBeanClass = Class.forName(className);
                    Object beanObj = controllerBeanClass.newInstance();
                    beanMap.put(beanId, beanObj);
                }
            }

        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (SAXException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置编码
        req.setCharacterEncoding("utf-8");

        // 假设url是： http://localhost:8080/day8/fruit.do
        //那么 servletPath 是: /hello.do
        // 具体思路是：
        // 第一步： 通过 getServletPath() 这个函数获取到了 /hello.do
        // 第二步： 通过字符串截取得到 hello
        // 第三步： 让hello 和 HelloController 能对应上
        String servletPath = req.getServletPath();  //通过这个函数获取到了 /hello.do
        //通过字符串截取得到 hello
        int lastDotIndex = servletPath.lastIndexOf(".do");
        servletPath = servletPath.substring(1, lastDotIndex);

        Object controllerBeanObj = beanMap.get(servletPath);


        String operate = req.getParameter("operate");
        if (StringUtil.isEmpty(operate)){
            operate = "index";
        }

        try {
            // 代表了获取了 controllerBeanObj 对象的类的所有声明的方法
            Method[] methods = controllerBeanObj.getClass().getDeclaredMethods();
            for (Method method : methods) {
                if (operate.equals(method.getName())){
                    // 1. 统一获取请求参数
                    // 1.1 获取当前方法的参数，返回参数数组  使用Java反射API获取 method 对象的参数
                    Parameter[] parameters = method.getParameters();
                    // 1.2 parameterValues 用来存放1.1中所获取的参数的值
                    // 这个数组可能被用来存储对应 parameters 数组的参数值，以便稍后调用 method 对象时使用。
                    Object[] parameterValues = new Object[parameters.length];
                    for (int i = 0; i < parameters.length; i++) {
                        Parameter parameter = parameters[i];
                        String parameterName = parameter.getName();
                        // 如果参数名是 req,resp,session 那么就不是通过请求中获取参数的方式了
                        if ("req".equals(parameterName)) {
                            parameterValues[i] = req;
                        }else if ("resp".equals(parameterName)){
                            parameterValues[i] = resp;
                        }else if ("session".equals(parameterName)){
                            parameterValues[i] = req.getSession();
                        }else {
                            // 从请求中获取参数值
                            String parameterValue = req.getParameter(parameterName);
                            // 通过调用parameter的getType()方法可以获取到该参数的类型信息，
                            // 进而再使用getName()方法获取该类型的名称，最终存储在typeName变量中
                            String typeName = parameter.getType().getName();

                            Object parameterObj = parameterValue;

                            if (parameterObj != null) {
                                // 判断是否存在 Integer 类型的，如果有则进行强制类型转换
                                // 因为如果不单独进行转换的话，有可能会报错
                                // 常见错误： IllegalArgumentException: argument type mismatch 类型不匹配
                                if ("java.lang.Integer".equals(typeName)) {
                                    parameterObj = Integer.parseInt(parameterValue);
                                }
                            }
                            parameterValues[i] = parameterObj;
                        }
                    }

                    // 2.Controller 组件中的方法调用
                    //将 Method 对象的 accessible 标志设置为 true，从而可以访问该方法，即使该方法是私有的或受保护的。
                    method.setAccessible(true);
                    // m.invoke() 方法会将 this 对象作为方法的调用者，也就是说，该方法会在当前对象上执行。
                    Object returnObj = method.invoke(controllerBeanObj, parameterValues);

                    String methodReturnStr = null;
                    // 3.视图处理
                    if (returnObj != null) {
                        methodReturnStr = (String) returnObj;
                    }
                    // 重定向
                    if (methodReturnStr.startsWith("redirect:")){  // 比如 "redirect:fruit.do"
                        String redirectStr = methodReturnStr.substring("redirect:".length());
                        resp.sendRedirect(redirectStr);
                    }else {
                        super.processTemplate(methodReturnStr,req,resp); // 比如 "edit"
                    }
                }
            }



//            }else {
//                throw new RuntimeException("operate的值非法！");
//            }
        }  catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }


    }
}


// 常见错误： IllegalArgumentException: argument type mismatch